/*#io 
Python ioDoc(
		 docCopyright("Aslak Gronflaten", 2006)
		 docLicense("BSD revised")
		 docDescription("This object provides access the world of python.")
		 docCredits("Based on code by Steve Dekorte")
		 docCategory("Server")
*/

#include "IoPython.h"
#include "PyIo.h"
#include "IoState.h"
#include "IoNumber.h"
#include "IoList.h"
#include "IoDirectory.h"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>


IoTag *IoPython_tag(void *state)
{
	IoTag *tag = IoTag_newWithName_("Python");
	tag->state = state;
	tag->cloneFunc = (TagCloneFunc *)IoPython_rawClone;
	tag->freeFunc = (TagFreeFunc *)IoPython_free;
	return tag;
}

IoPython *IoPython_proto(void *state)
{
	Py_Initialize();
	IoObject *self = IoObject_new(state);
	self->tag = IoPython_tag(state);
	IoObject_setDataPointer_(self, PythonData_new());
	fflush(stdout);
	IoState_registerProtoWithFunc_(state, self, IoPython_proto);
	
	{
		IoMethodTable methodTable[] = {
		{"credits", IoPython_credits},
		{"forward", IoPython_forward},
		{"import", IoPython_import},
		{"invoke", IoPython_invoke},
		{"print", IoPython_print},
		{"println", IoPython_println},
		{"self", IoObject_self},
		{"new", IoPython_newInstance},
		{"eval", IoPython_eval},
		{NULL, NULL},
		};
		IoObject_addMethodTable_(self, methodTable);
	}
	DATA(self)->data = (void *) 1; // Hack
	initpyIo(); // Initialize python io wrapper.
	return self;
}

IoPython *IoPython_rawClone(IoPython *proto) 
{ 
	IoPython *self = IoObject_rawClonePrimitive(proto);
	IoObject_setDataPointer_(self, PythonData_new());
	return self;
}

/* ----------------------------------------------------------- */

IoPython *IoPython_new(void *state)
{
	IoObject *proto = IoState_protoWithInitFunction_(state, IoPython_proto);
	return IOCLONE(proto);
}

void IoPython_free(IoPython *self)
{
	PythonData_free(DATA(self));
}
/* ----------------------------------------------------------- */

#include <python2.4/Python.h>

IoObject *wrap(IoPython *self, PyObject *o) {
	IoPython *ret = IoPython_new(self->state);
	DATA(ret)->data = o;
	return ret; 
}

IoObject *IoPython_credits(IoPython *self, IoObject *locals, IoMessage *m)
{
	return IOSYMBOL("Python/Io bridge by Aslak Gronflaten");
}

IoObject *IoPython_print(IoPython *self, IoObject *locals, IoMessage *m)
{
	printf("|");
	PyObject *obj = DATA(self)->data;
	if(obj > (PyObject *) 1) {
		PyObject_Print(obj, stdout, 0);
	}
	printf("|");
	return self;
}
IoObject *IoPython_println(IoPython *self, IoObject *locals, IoMessage *m) {
	IoPython_print(self, locals, m);
	printf("\n");
	return self;
}

/**
 * Convert an Io object to PyObject.
 * At the moment, we can't pass in any objects, just those that can be translated,
 * until I build a python wrapper around an io object, reverse of what I did here.
 * TODO: Memory management!!!
 */
PyObject *convertIo(IoObject *self, IoObject *obj) {
	PyObject *ret;
	if(ISNIL(obj)) {
		ret = Py_None;
	} else if(ISNUMBER(obj)) {
		float f = CNUMBER(obj);
		long fi = floor(f);
		if((fi - f) < 0.000001)
			ret = PyInt_FromLong(fi);
		else
			ret = PyFloat_FromDouble(f);
	} else if(ISSEQ(obj)) {
		ret = PyString_FromString(CSTRING(obj));
	} else if(ISIOPYTHON(obj)) {
		if(DATA(obj)->data)
			ret = DATA(obj)->data;  // Return the object we're wrapping
		else
			ret =  newPyIo(obj);  // TODO: Incref?  We're a subclass. Wrap up.
	} else {
		ret = newPyIo(obj);
	}
	Py_INCREF(ret);
	return ret;
}

/**
 * Tries to convert the obj to an Io object, if possible. If not, return null.
 * Can't decref if this method is used on both pFunc and pValue
 */
IoObject *convertPy(IoObject *self, PyObject *obj) {
	IoObject *ret;  
	if(obj == Py_None) {
		ret = IONIL(self);
	} else if(PyString_Check(obj)) {
		// Convert to Io sequence and return.
		ret = IoSeq_newWithCString_(self->state, PyString_AsString(obj));
		// TODO:::: Memory management! Who's responsible here for that pystring? (I am, that's who)
	} else if(PyFloat_Check(obj)) {
		ret = IoNumber_newWithDouble_(self->state, PyFloat_AS_DOUBLE(obj));
	} else if(PyInt_Check(obj)) {
		ret = IoNumber_newWithDouble_(self->state, PyInt_AS_LONG(obj));
		// Decref?
	} else if(PyList_Check(obj)) {
		// We have a list. So, make an Io list, and convert every element, and insert them.
		/* Explicit conversion is prefered
		int len = PyList_GET_SIZE(obj);
		ret = IoList_new(self->state);
		int i;
		for(i=0;i<len;i++) {
			PyObject *o = PyList_GET_ITEM(obj, i);
			IoObject *x = convertPy(self, o);
			// insert in list
			IoList_rawAppend_(ret, x);
		}
		return ret;
		*/
	} else if(PyDict_Check(obj)) {
		// We have a dictionary. Make an Io map, and convert all values.
		// or should we.... Io's map can only have string keys... hardly a replacement, now is it.
		// Would be better to build a good wrapper around the python dict.
	} else if(PyCallable_Check(obj)) {
		// TODO: We should return a callable object here... PyIoMethod
	}
	return wrap(self, obj);
}

static int callNum = 0;

IoObject *IoPython_call_int(IoPython *self, IoObject *locals, IoMessage *m, int argOffset, char *functionName) 
{	
	if(!functionName) {
		fprintf(stderr, "Null function name in call_int\n");
		return IONIL(self);
	}
	int argc = IoMessage_argCount(m);
	IoObject *ret; 
	PyObject *pModule = DATA(self)->data;

	if(!pModule) {
		// This happens if we're in a subclass of a python class. Forward to supers.
		callNum++;
		if(callNum > 2) {
			fprintf(stderr, "Recursion in forward. Returning nil.");
			return IONIL(self);
		}
		// TODO: Not good enough - won't have the correct context (we loose the self)
		// The more correct call is IoObject_super - but it's gone!
		ret = IoObject_forward(IoObject_firstProto(self), locals, m);
		callNum = 0;
		return ret;
	}

	
	PyObject *pFunc;
	
	if(!PyObject_HasAttrString(pModule, functionName)){
		fprintf(stderr, "Module has no function %s, argoffset=%i\n", functionName, argOffset);
		if(argOffset == 0) {
			return forwardToParent(self, locals, m);
		}
		return IONIL(self);
	}
	pFunc = PyObject_GetAttrString(pModule, functionName);
	/* pFunc is a new reference */

	if (pFunc && (PyFunction_Check(pFunc) || PyCFunction_Check(pFunc) || PyMethod_Check(pFunc))) { // We have a function or method. Call it
		fflush(stdout);
    	PyObject *pArgs = PyTuple_New(argc - argOffset); // argc
		int i;
		for(i = argOffset;i<argc;i++) {
			IoObject *param = IoMessage_locals_valueArgAt_(m, locals, i);
			PyObject *pyValue = convertIo(self, param);
			PyTuple_SetItem(pArgs, i-argOffset, pyValue);
		}
    	PyObject *pValue = PyObject_CallObject(pFunc, pArgs);
    	Py_DECREF(pArgs);
		Py_XDECREF(pFunc);
    	if (pValue != NULL) {
			return convertPy(self, pValue);
    	} else {
	    	if (PyErr_Occurred())
        		PyErr_Print();
        	fprintf(stderr,"Call failed\n");
    	}
	} else {
    	if (PyErr_Occurred())
        	PyErr_Print();
		else {
			return convertPy(self, pFunc);
		}
    	fprintf(stderr, "Cannot find python function \"%s\"\n", functionName);
	}
	return IONIL(self);
}
IoObject *forwardToParent(IoPython *self, IoObject *locals, IoMessage *m) {
	IoObject *parent = IoObject_firstProto(self);
	if(parent) {
		return IoObject_forward(parent, locals, m);
	} else {
		printf("We have no parent, yet no python data. Invalid object.");
		fprintf(stderr, "Invalid IoPython object state.\n");
		return IONIL(self);
	}
}

// Call self
IoObject *IoPython_newInstance(IoPython *self, IoObject *locals, IoMessage *m) {
	int argc = IoMessage_argCount(m);
	IoObject *ret; 
	PyObject *obj = DATA(self)->data;
	if(!obj) {
		IoObject *parent = IoObject_firstProto(self);
		if(parent) {
			if(ISIOPYTHON(parent)) {
				IoObject *ny = IoPython_newInstance(parent, locals, m);
				if(ISNIL(ny)) {
					// Some error occured.
					return ny;
				}
				// Now clone that instance, and add self as proto
				ret = IoObject_clone(ny, locals, m);
				IoObject_rawAppendProto_(ret, self);
				return ret;
			} else {
				return IoObject_forward(parent, locals, m);
			}
		} else {
			printf("We have no parent, yet no python data. Invalid object.");
			fprintf(stderr, "Invalid IoPython object state.\n");
			return IONIL(self);
		}
	}
	if(!PyCallable_Check(obj)) {
		fprintf(stderr, "Object is not callable this is an error, so we print\n");
		PyObject_Print(obj, stdout, 0);
		return IONIL(self);
	}
	PyObject *pArgs = PyTuple_New(argc);
	int i;
	for(i = 0;i<argc;i++) {
		IoObject *param = IoMessage_locals_valueArgAt_(m, locals, i);
		PyObject *pyValue = convertIo(self, param);
		PyTuple_SetItem(pArgs, i, pyValue);
	}
	PyObject *result = PyObject_CallObject(obj, pArgs);
	if(!result) {
		fprintf(stderr, "Result is null in newInstance!\n");
		if (PyErr_Occurred())
        	PyErr_Print();
		return IONIL(self);
	}
	Py_DECREF(pArgs);
	Py_XINCREF(result);
	return wrap(self, result);
}

IoObject *IoPython_forward(IoPython *self, IoObject *locals, IoMessage *m)
{
	IoSymbol *name = IoMessage_name(m);
	char *functionName = IoSeq_asCString(name);
	//printf("IoPython_forward called - message: %s\n", functionName);
	return IoPython_call_int(self, locals, m, 0, functionName);
}

IoObject *IoPython_invoke(IoPython *self, IoObject *locals, IoMessage *m)
{
	IoSeq *name = IoMessage_locals_seqArgAt_(m, locals, 0);
	char *functionName = IoSeq_asCString(name);
	return IoPython_call_int(self, locals, m, 1, functionName);
}

// Evaluate a string as python code
// N.B: Doesn't actually do this yet, just runs the code
IoObject *IoPython_eval(IoPython *self, IoObject *locals, IoMessage *m)
{
	IoSeq *codeString = IoMessage_locals_seqArgAt_(m, locals, 0);
	char *code = IoSeq_asCString(codeString);
	int result = PyRun_SimpleString(code);
	return IoNumber_newWithDouble_(self->state, result);
}

/**
 * Import a module, return reference to it as a Python object
 */
IoObject *IoPython_import(IoPython *self, IoObject *locals, IoMessage *m)
{
	IoSeq *name = IoMessage_locals_seqArgAt_(m, locals, 0);
	char *nameString = IoSeq_asCString(name);
	
    PyObject *pName, *pModule;
 	pName = PyString_FromString(nameString);
    /* Error checking of pName left out */

    pModule = PyImport_Import(pName);

	if(!pModule) {
		fprintf(stderr, "Could not find module %s\n", nameString);
		return IONIL(self);
	}

	// Set slots (for easier introspection and use from io) TODO: Complete this
	/*
	PyObject *dict = PyModule_GetDict(pModule);
	PyObject *keys = PyDict_Keys(dict);
	int i;
	for(i = 0;i<PyList_Size(keys);i++) {
		PyObject *key = PyList_GetItem(keys, i);
		PyObject *value = PyDict_GetItem(dict, key);
		// TODO: Not allowed method vall IoSeq_newSymbolWithCString_
		if(!PyCallable_Check(value)) {// don't want methods blocking the forward
			IoObject_setSlot_to_(self, IOSYMBOL(PyString_AsString(key)), convertPy(self, value));
		}
	}
	*/
	//

    Py_DECREF(pName);	
	return wrap(self, pModule);
}

